package org.kiwi.data.util;

import com.alibaba.fastjson.JSON;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.kiwi.data.adapter.druid.DruidDataSourceAdapter;
import org.kiwi.data.enums.DbType;
import org.kiwi.data.enums.PoolType;
import org.kiwi.data.vo.DataSourceMetaVO;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.ManagedMap;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.ParserContext;
import org.w3c.dom.Element;

import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

import static org.kiwi.data.cons.DataConstant.*;

/**
 * Created by jack08.liu on 2016/12/24.
 */
public class BeanDefinitionParserUtil {

    public static DataSourceMetaVO resolve(Element element, ParserContext parserContext) {
        String dbConfig = PropertiesUtil.getDbConfig();// TODO: 16/10/30 other module provides implementation

        String id = element.getAttribute(ATTRIBUTE_ID);
        if (StringUtils.isBlank(id)) {
            parserContext.getReaderContext().error("datasource id must not be null, Please check id.", parserContext.extractSource(element));
        }

        DataSourceMetaVO dataSourceMetaVO = JSON.parseObject(dbConfig, DataSourceMetaVO.class);
        dataSourceMetaVO.setId(id);

        return dataSourceMetaVO;
    }

    public static void validate(DataSourceMetaVO dataSourceMetaVO) {
        if (CollectionUtils.isEmpty(dataSourceMetaVO.getGroups())) {
            throw new IllegalArgumentException("groups is required");
        } else {
            if (StringUtils.isAnyBlank(dataSourceMetaVO.getDbType(), dataSourceMetaVO.getId(), dataSourceMetaVO.getState())) {
                throw new IllegalArgumentException("required parameter missing in dataSourceMetaVO :[" + JSON.toJSONString(dataSourceMetaVO) + "]");
            }

            for (DataSourceMetaVO.GroupDataSourceMetaVO groupDataSourceMetaVO : dataSourceMetaVO.getGroups()) {
                if (StringUtils.isAnyBlank(groupDataSourceMetaVO.getGroupName(), groupDataSourceMetaVO.getState(),
                        groupDataSourceMetaVO.getLoadBalance())) {
                    throw new IllegalArgumentException("required parameter missing in dataSourceMetaVO :[" + JSON.toJSONString(dataSourceMetaVO) + "]");
                }

                int writeDsCount = 0;

                for (DataSourceMetaVO.ReadWriteMetaVO readWriteMetaVO : groupDataSourceMetaVO.getAtomicDss()) {
                    if (StringUtils.isAnyBlank(readWriteMetaVO.getState(), readWriteMetaVO.getDsName(),
                            readWriteMetaVO.getHost(), readWriteMetaVO.getPassword(), readWriteMetaVO.getPort(),
                            readWriteMetaVO.getScheme(), readWriteMetaVO.getUsername(), readWriteMetaVO.getPoolType())) {
                        throw new IllegalArgumentException("required parameter missing in dataSourceMetaVO :[" + JSON.toJSONString(dataSourceMetaVO) + "]");
                    }

                    if (readWriteMetaVO.getParam() == null) {
                        throw new IllegalArgumentException("connection param must not be null");
                    }

                    if (readWriteMetaVO.getLbWeight() == null || readWriteMetaVO.getLbWeight() <= 0) {
                        throw new IllegalArgumentException("lbWeight must not be null and must be positive integer");
                    }

                    if (readWriteMetaVO.getState() == null) {
                        throw new IllegalArgumentException("db state must not be null");
                    }

                    if (readWriteMetaVO.getIsMaster()) {
                        writeDsCount++;
                    }
                }

                if (writeDsCount != 1) {
                    throw new IllegalArgumentException("every group write ds must not be null and only one");
                }
            }
        }
    }

    public static void registerConfigDataSource(ParserContext parserContext,
                                                DataSourceMetaVO dataSourceMetaVO,
                                                Map<String, ManagedMap<String, RuntimeBeanReference>> groupDsMap,
                                                Map<String, String> writeDsBeanNameMap) {

        registerConfigDataSource(parserContext, dataSourceMetaVO, groupDsMap, writeDsBeanNameMap, null);
    }

    public static void setUpAtomicDataSourceMetaInfo(ParserContext parserContext,
                                                     DataSourceMetaVO dataSourceMetaVO,
                                                     Map<String, String> writeDsBeanNameMap,
                                                     Map<String, Map<String, Integer>> groupLbFactorsMap) {

        registerConfigDataSource(parserContext, dataSourceMetaVO, null, writeDsBeanNameMap, groupLbFactorsMap);
    }

    public static void registerConfigDataSource(ParserContext parserContext,
                                                DataSourceMetaVO dataSourceMetaVO,
                                                Map<String, ManagedMap<String, RuntimeBeanReference>> groupDsMap,
                                                Map<String, String> writeDsBeanNameMap,
                                                Map<String, Map<String, Integer>> groupLbFactorsMap) {
        for (DataSourceMetaVO.GroupDataSourceMetaVO groupDataSourceMetaVO : dataSourceMetaVO.getGroups()) {

            String groupName = groupDataSourceMetaVO.getGroupName();

            if (groupDsMap != null) {
                groupDsMap.put(groupName, new ManagedMap<String, RuntimeBeanReference>());
            }

            if (groupLbFactorsMap != null) {
                groupLbFactorsMap.put(groupName, new HashMap<String, Integer>());
            }

            for (DataSourceMetaVO.ReadWriteMetaVO readWriteMetaVO : groupDataSourceMetaVO.getAtomicDss()) {

                String dsBeanName = groupName + "_" + readWriteMetaVO.getDsName();

                if (!parserContext.getRegistry().containsBeanDefinition(dsBeanName)) {
                    registerAtomicDataSource(parserContext, dataSourceMetaVO, readWriteMetaVO, dsBeanName);
                }

                if (groupDsMap != null) {
                    groupDsMap.get(groupName).put(dsBeanName, new RuntimeBeanReference(dsBeanName));
                }

                if (readWriteMetaVO.getIsMaster()) {
                    writeDsBeanNameMap.put(groupName, dsBeanName);
                } else {
                    if (groupLbFactorsMap != null) {
                        groupLbFactorsMap.get(groupName).put(dsBeanName, readWriteMetaVO.getLbWeight());
                    }
                }
            }
        }
    }

    private static void registerAtomicDataSource(ParserContext parserContext,
                                                 DataSourceMetaVO dataSourceMetaVO,
                                                 DataSourceMetaVO.ReadWriteMetaVO readWriteMetaVO,
                                                 String dsBeanName) {
        if (PoolType.determinePoolTypeByText(readWriteMetaVO.getPoolType()) != PoolType.DRUID) {
            throw new UnsupportedOperationException("unsupported pool type");
        }

        RootBeanDefinition dsBeanDefinition = new RootBeanDefinition();
        dsBeanDefinition.setBeanClassName(DruidDataSourceAdapter.class.getName());

        dsBeanDefinition.setInitMethodName(DEFAULT_INIT_METHOD);
        dsBeanDefinition.setDestroyMethodName(DEFAULT_DESTROY_METHOD);

        String url;
        String urlContent = readWriteMetaVO.getHost() + ":" + readWriteMetaVO.getPort() + "/"
                + readWriteMetaVO.getScheme();
        String urlCharset = "?" + DB_CONNECTION_COMMON_PARAM;

        DbType dbType = DbType.determineDbTypeByText(dataSourceMetaVO.getDbType());
        if (dbType == DbType.MYSQL) {
            url = JDBC_MYSQL_PREFIX + urlContent + urlCharset;
            if (StringUtils.isNotBlank(readWriteMetaVO.getParam())) {
                url += "&" + readWriteMetaVO.getParam();
            }
            dsBeanDefinition.getPropertyValues().add(JDBC_DRIVER_CLASS_NAME_PROP, MYSQL_JDBC_DRIVER);

        } else if (dbType == DbType.ORACLE) {
            url = JDBC_ORACLE_PREFIX + urlContent;
            dsBeanDefinition.getPropertyValues().add(JDBC_DRIVER_CLASS_NAME_PROP, MYSQL_ORACLE_DRIVER);

        } else if (dbType == DbType.DB2) {
            url = JDBC_DB2_PREFIX + urlContent;
            dsBeanDefinition.getPropertyValues().add(JDBC_DRIVER_CLASS_NAME_PROP, MYSQL_DB2_DRIVER);

        } else {
            throw new UnsupportedOperationException("unsupported db type");
        }

        dsBeanDefinition.getPropertyValues().add(JDBC_URL_PROP, url);
        dsBeanDefinition.getPropertyValues().add(JDBC_USERNAME_PROP, readWriteMetaVO.getUsername());
        dsBeanDefinition.getPropertyValues().add(JDBC_PASSWORD_PROP, readWriteMetaVO.getPassword());

        // TODO: 16/10/31 xml config can override properties file
        Properties poolConfig = readWriteMetaVO.getPoolConfig();
        if (poolConfig != null) {
            dsBeanDefinition.getPropertyValues().addPropertyValues(poolConfig);
        } else {
            dsBeanDefinition.getPropertyValues().add(INITIAL_SIZE_PROP, DEFAULT_INITIAL_SIZE);
            dsBeanDefinition.getPropertyValues().add(MAX_ACTIVE_PROP, DEFAULT_MAX_ACTIVE);
            dsBeanDefinition.getPropertyValues().add(MIN_IDLE_PROP, DEFAULT_MIN_IDLE);
            dsBeanDefinition.getPropertyValues().add(MAX_WAIT_PROP, DEFAULT_MAX_WAIT);
            dsBeanDefinition.getPropertyValues().add(VALIDATION_QUERY_PROP, DEFAULT_VALIDATION_QUERY);
            dsBeanDefinition.getPropertyValues().add(TEST_WHILE_IDLE_PROP, DEFAULT_TEST_WHILE_IDLE);
            dsBeanDefinition.getPropertyValues().add(TEST_ON_BORROW_PROP, DEFAULT_TEST_ON_BORROW);
            dsBeanDefinition.getPropertyValues().add(TEST_ON_RETURN_PROP, DEFAULT_TEST_ON_RETURN);
            dsBeanDefinition.getPropertyValues().add(POOL_PREPARED_STATEMENTS_PROP, DEFAULT_POOL_PREPARED_STATEMENTS);
            dsBeanDefinition.getPropertyValues().add(MAX_POOL_PREPARED_STATEMENT_PER_CONNECTION_SIZE_PROP, DEFAULT_MAX_POOL_PREPARED_STATEMENT_PER_CONNECTION_SIZE);
            dsBeanDefinition.getPropertyValues().add(FILTERS_PROP, DEFAULT_FILTERS);
        }

        parserContext.getRegistry().registerBeanDefinition(dsBeanName, dsBeanDefinition);
    }


}
